package apiserver

import (
	"context"
	"fmt"

	"gitee.com/youkelike/ziam/gshutdown"
	"gitee.com/youkelike/ziam/zoptions/options"
	"gitee.com/youkelike/ziam/zorm/store/mysql"
	"gitee.com/youkelike/ziam/zredis"
	"gitee.com/youkelike/ziam/zserver"
	"gitee.com/youkelike/zlog"
)

type apiServer struct {
	gs               *gshutdown.GracefulShutdown
	redisOptions     *options.RedisOptions
	gRPCAPIServer    *grpcAPIServer
	genericAPIServer *zserver.GenericAPIServer
	redisCancelFunc  context.CancelFunc
}

func createAPIServer(cfg *Config) (*apiServer, error) {
	gs := gshutdown.New()

	// http 和 https 服务
	genericConfig, err := buildGenericConfig(cfg)
	if err != nil {
		return nil, err
	}
	genericServer, err := genericConfig.Complete().New()
	if err != nil {
		return nil, err
	}

	// grpc 服务
	extraConfig, err := buildExtraConfig(cfg)
	if err != nil {
		return nil, err
	}
	extraServer, err := extraConfig.Complete().New()
	if err != nil {
		return nil, err
	}

	server := &apiServer{
		gs:               gs,
		redisOptions:     cfg.RedisOptions,
		genericAPIServer: genericServer,
		gRPCAPIServer:    extraServer,
	}

	return server, nil
}

func (s *apiServer) PrepareRun() *apiServer {
	initRouter(s.genericAPIServer.Engine)

	ctx, cancel := context.WithCancel(context.Background())
	s.redisCancelFunc = cancel
	// 创建 redis 客户端单例的同时，每隔 1 秒检查 redis 客户端是否可用，不可用会自动尝试重连
	go zredis.ConnectToRedis(ctx, s.buildRedisConfig())

	// 集中处理所有服务的优雅关停回调
	s.gs.AddShutdownCallback(gshutdown.ShutdownFunc(func() error {
		// 关闭 mysql，它是在 gRPCAPIServer 启动的时候打开的
		mysqlStore, _ := mysql.GetMysqlFactoryOr(nil)
		if mysqlStore != nil {
			mysqlStore.Close()
			zlog.Infow("mysql closed")
		}

		// 关闭 grpc 服务
		s.gRPCAPIServer.Close()
		zlog.Infow("grpc closed")

		// 关闭 http https 服务
		s.genericAPIServer.Close()
		zlog.Infow("apiserver closed")

		// 关闭 redis
		s.redisCancelFunc()
		zlog.Infow("redis cancelled")

		return nil
	}))

	return s
}

func (s *apiServer) buildRedisConfig() *zredis.Config {
	return &zredis.Config{
		Host:                  s.redisOptions.Host,
		Port:                  s.redisOptions.Port,
		Addrs:                 s.redisOptions.Addrs,
		MasterName:            s.redisOptions.MasterName,
		Username:              s.redisOptions.Username,
		Password:              s.redisOptions.Password,
		Database:              s.redisOptions.Database,
		MaxIdle:               s.redisOptions.MaxIdle,
		MaxActive:             s.redisOptions.MaxActive,
		Timeout:               s.redisOptions.Timeout,
		EnableCluster:         s.redisOptions.EnableCluster,
		UseSSL:                s.redisOptions.UseSSL,
		SSLInsecureSkipVerify: s.redisOptions.SSLInsecureSkipVerify,
	}
}

func (s apiServer) Run() (err error) {
	go s.gRPCAPIServer.Run()
	go func() {
		err = s.genericAPIServer.Run()
	}()

	s.gs.Start()
	defer s.gs.Wait(context.TODO())

	return err
}

func buildGenericConfig(cfg *Config) (genericConfig *zserver.Config, err error) {
	genericConfig = zserver.NewConfig()
	if err = cfg.GenericServerRunOptions.ApplyTo(genericConfig); err != nil {
		return
	}

	if err = cfg.FeatureOptions.ApplyTo(genericConfig); err != nil {
		return
	}

	if err = cfg.SecureServing.ApplyTo(genericConfig); err != nil {
		return
	}

	if err = cfg.InsecureServing.ApplyTo(genericConfig); err != nil {
		return
	}

	return
}

func buildExtraConfig(cfg *Config) (*ExtraConfig, error) {
	return &ExtraConfig{
		Addr:         fmt.Sprintf("%s:%d", cfg.GRPCOptions.BindAddress, cfg.GRPCOptions.BindPort),
		MaxMsgSize:   cfg.GRPCOptions.MaxMsgSize,
		ServerCert:   cfg.SecureServing.ServerCert,
		mysqlOptions: cfg.MySQLOptions,
		// etcdOptions:      cfg.EtcdOptions,
	}, nil
}
